package manager

import (
    "../config"
    SSH "../tools/ssh"
    "../tools/zip"
    "../tools/shell/console"
    "../tools/paths"
    . "fmt"
    uuid "github.com/satori/go.uuid"
    "io/ioutil"
    "os"
    "path"
    "strings"
    "time"
)

type Play struct {
    ProjectDir  string
    ProjectName string
    Profile     config.IniFile
    Affirm      bool
    tempWorkDir string
    projectZip  string
    Sudo        bool
}

type serverEnvironment struct {
    unzip string
    java  string
    play  string
    dir   string
    tmp   string
}

func (play Play) DoPublic() {
    //旧工程目录/新工程展开后的目录
    unfoldDir := path.Join(play.Profile.Dir, play.ProjectName)
    backInfo := "工程备份: 关\n"
    if play.Profile.LocalBackUp {
        backInfo = Sprintf("工程备份目录:%s\n", play.Profile.LocalBackDir)
    }
    sudoMode := "--"
    if play.Sudo {
        sudoMode = "sudo"
    }
    if play.Profile.IsIrrational() {
        return
    }
    confirmText := Sprintf(`请确认信息:
框架:%s
待部署的工程: %s
部署模式: %s
部署启动模式: %s
部署服务器用户: %s
部署服务器: %s
部署服务器路径: %s
部署前执行: %s
%s`, play.Profile.Framework, play.ProjectDir, play.Profile.Mode, sudoMode, play.Profile.User, strings.Join(play.Profile.Host, " "), unfoldDir, strings.Join(play.Profile.Exec, " "), backInfo)
    Printf(confirmText)
    if !play.Affirm {
        input := strings.TrimSpace(strings.ToLower(console.Input("按下回车开始执行!(N or Exit 退出)")))
        if "exit" == input || "n" == input {
            return
        }
        //cmd := exec.Command("clear") //Linux example, its tested
        //cmd.Stdout = os.Stdout
        //_ = cmd.Run()
        console.Clear()
    }
    //创建本地临时工作目录
    _uuid, err := uuid.NewV1()
    if err != nil {
        Printf("ERROR : %s\n", err)
        return
    }
    play.tempWorkDir = Sprintf("/tmp/deployment-%s", _uuid.String())
    err = paths.Mkdirs(play.tempWorkDir)
    if err != nil {
        Printf("ERROR : %s\n", err)
        return
    }
    defer play.clean()

    Printf("正在检查远端服务器基本环境\n")
    for _, host := range play.Profile.Host {
        ssh := SSH.SysSSH{User: play.Profile.User, Host: host, Port: 22}
        var result SSH.SSHResult
        var sudoMode string
        if "root" != play.Profile.User {
            if "" == play.Profile.RootPassword {
                sudoMode = "sudo "
            } else {
                sudoMode = Sprintf(`echo "%s" | sudo -S `, play.Profile.RootPassword)
            }
        }
        initCmd := Sprintf(`source /etc/profile .bash_profile;UNZIP_PATH=$(which unzip);if [[ -z "${UNZIP_PATH}" ]];then echo "NO UNZIP" && exit 1;fi;JAVA_PATH=$(which java);if [[ -z "${JAVA_PATH}" ]];then echo "NO JAVA" && exit 1;fi;PLAY_PATH=$(which play);if [[ -z "${PLAY_PATH}" ]];then echo "NO PLAY" && exit 1;fi;if [[ ! -e "%s" ]];then %smkdir "%s" && %schmod 777 "%s" && exit 1;fi;mkdir "%s";echo "UNZIP=${UNZIP_PATH}" && echo "JAVA=${JAVA_PATH}" && echo "PLAY=${PLAY_PATH}"`, play.Profile.Dir, sudoMode, play.Profile.Dir, sudoMode, play.Profile.Dir, play.tempWorkDir)
        result = ssh.Exe(initCmd)
        if result.Code != 0 {
            errInfo := result.GetErr()
            if strings.Contains(errInfo, "Permanently added") && strings.Contains(errInfo, "to the list of known hosts") {
                Printf("添加SSH信任失败,请尝试手动执行:`ssh %s@%s \"pwd\"`\n", play.Profile.User, host)
            } else {
                Printf("远程[%s]环境检查未通过:%s\n", host, errInfo)
            }
            return
        }
        envStr := strings.Split(result.StdOut, "\n")
        var envir serverEnvironment
        for _, line := range envStr {
            envs := strings.Split(line, "=")
            switch envs[0] {
            case "UNZIP":
                envir.unzip = envs[1]
            case "JAVA":
                envir.java = envs[1]
            case "PLAY":
                envir.play = envs[1]
            }
        }
        envir.dir = play.Profile.Dir
        envir.tmp = play.tempWorkDir
        Printf("正在部署到服务器[%s]\n", host)
        play.projectZip, err = play.pack()
        if err != nil {
            envir.clearServer(ssh)
            return
        }
        Printf("正在上传工程文件到服务器[%s]\n", host)
        //result = ssh.UploadVisible(play.projectZip, envir.tmp, play.tempWorkDir)
        result = ssh.Upload(play.projectZip, envir.tmp)
        if result.Code != 0 {
            Printf("上传工程时发生错误:%s\n", result.GetErr())
        }
        //上传到服务器的Zip文件
        serverZip := path.Join(envir.tmp, path.Base(play.projectZip))
        findTar := Sprintf(`if [[ -e "%s" ]];then echo "0" ;else echo "1";fi;`, unfoldDir)
        result = ssh.Exe(findTar)
        if result.Code != 0 {
            Printf("ERR:%s\n", result.GetErr())
            envir.clearServer(ssh)
            return
        } else if 0 == result.Code {
            if "0" == strings.TrimSpace(result.StdOut) {
                Printf("正在删除旧工程\n")
                args := make([]string, 0)
                /*
                   命令组1 -- 停止就工程
                   1-1.进入旧工程目录
                   1-2.执行停止命令
                   1-3.使用ps命令再次停止工程
                   1-4.删除旧工程
                */
                //1-1.进入旧工程目录
                args = append(args, Sprintf("cd %s", unfoldDir))
                //1-2.使用框架命令停止工程
                if play.Sudo {
                    args = append(args, Sprintf("sudo %s stop", envir.play))
                    //1-3.使用ps命令再次停止工程
                    args = append(args, Sprintf("ps -ef | grep play | grep /%s/conf | grep -v grep | awk '{print $2}' | xargs sudo kill -9", play.ProjectName))
                } else {
                    args = append(args, Sprintf("%s stop", envir.play))
                    //1-3.使用ps命令再次停止工程
                    args = append(args, Sprintf("ps -ef | grep play | grep /%s/conf | grep -v grep | awk '{print $2}' | xargs kill -9", play.ProjectName))
                }
                //1-4-1.回到主目录
                args = append(args, "cd")
                //1-4-2.删除旧工程
                args = append(args, Sprintf("rm %s -rf", unfoldDir))
                result = ssh.Exe(args...)
                if result.Code != 0 {
                    errStr := result.GetErr()
                    if !strings.Contains(errStr, "play!") && !strings.Contains(errStr, "is stopped") {
                        Printf("停止旧工程时发生错误:%s\n", errStr)
                        envir.clearServer(ssh)
                        return
                    }
                }
            }
        }
        Printf("正在部署新工程\n")
        args := make([]string, 0)
        /*
           命令组2 -- 部署新工程
           2-1.解压缩工程到目标目录
           2-2.进入到工程目录
           2-3加入自定义命令
           2-4.启动工程
        */
        //2-1.解压缩文件(指定路径 -d,不打印日志 -q)
        //Println("--->", Sprintf("%s -q -d %s %s", envir.unzip, play.Profile.Dir, serverZip))
        args = append(args, Sprintf("%s -q -d %s %s", envir.unzip, play.Profile.Dir, serverZip))
        //2-2.进入到工程目录
        args = append(args, Sprintf("cd %s", unfoldDir))
        //2-3加入自定义命令
        args = append(args, play.Profile.Exec...)
        //2-4.启动工程
        if play.Sudo {
            args = append(args, Sprintf("sudo %s start", envir.play))
        } else {
            args = append(args, Sprintf("%s start", envir.play))
        }
        // 防止sock未能断开
		args = append(args, "echo $'\\003'")
        result = ssh.Exe(args...)
        if result.Code != 0 {
            Printf("部署新工程时发生错误:%s\n", result.GetErr())
            envir.clearServer(ssh)
            return
        }
        Printf("新工程在服务器[%s]上部署成功!\n%s\n", host, result.StdOut)

        Printf("正在检查新工程状态\n")
        /*
           命令组3 -- 检查新工程运行情况,并清理临时目录
        */
        for a := 0; a < 5; a++ {
            time.Sleep(time.Duration(1) * time.Second)
            Print(".")
        }
        Print("\r\n")
        args = make([]string, 0)
        args = append(args, Sprintf(`PID=$(ps -ef | grep play | grep -v grep | grep /%s/conf | awk '{print $2}');echo "PID=${PID}"`, play.ProjectName))
        result = ssh.Exe(args...)
        if result.Code != 0 {
            Printf("检查新工程时发生错误:%s\n", result.GetErr())
            envir.clearServer(ssh)
            return
        }
        pidArr := strings.Split(strings.TrimSpace(result.StdOut), "=")
        if len(pidArr) < 2 {
            Printf("新工程未通过检查:没有找到PID!\n")
            envir.clearServer(ssh)
            return
        }
        Printf("新工程检查通过!PID:%s\n", pidArr[1])
        envir.clearServer(ssh)
    }
}

func (play Play) getNoZips() []string {
    projectNozipFile := path.Join(play.ProjectDir, config.NozipFileName)
    noZips := make([]string, 0)
    if paths.Exists(projectNozipFile) {
        projectGitignoreContent, err := ioutil.ReadFile(projectNozipFile)
        if err != nil {
            Printf("读取[%s]时出错", projectNozipFile)
        } else {
            projectNozipFileContent := string(projectGitignoreContent)
            for _, line := range strings.Split(projectNozipFileContent, "\n") {
                line = strings.Split(line, "#")[0]
                line = strings.TrimSpace(line)
                if line != "" {
                    noZips = append(noZips, line)
                }
            }
        }
    }
    return noZips
}

func (play Play) pack() (string, error) {
    if play.projectZip == "" {
        projectZipFile := path.Join(play.tempWorkDir, play.ProjectName+".zip")
        Printf("正在打包工程文件[%s]\n", projectZipFile)
        err := zip.CompressDir(play.ProjectDir, projectZipFile, play.getNoZips())
        if err != nil {
            Printf("打包工程文件时发生错误:%s\n", err)
            return "", err
        }
        play.projectZip = projectZipFile
        if play.Profile.LocalBackUp {
            if !paths.Exists(play.Profile.LocalBackDir) {
                err = paths.Mkdirs(play.Profile.LocalBackDir)
                if err != nil {
                    Printf("创建工程备份目录失败:%s\n", err)
                    return play.projectZip, nil
                }
            }
            //备份文件:工程名#模式#时间.zip
            backFileName := Sprintf("%s#%s#%s.zip", play.ProjectName, play.Profile.Mode, time.Now().Format("2006-01-02@15:04:05"))
            backFilePath := path.Join(play.Profile.LocalBackDir, backFileName)
            err = paths.CopyFile(projectZipFile, backFilePath)
            if err != nil {
                Printf("备份工程文件失败:%s\n", err)
            } else {
                Printf("工程已备份到[%s]\n", backFilePath)
            }
        }
    }
    return play.projectZip, nil
}

func (play Play) clean() {
    Printf("清理临时文件...\n")
    Printf("")
    if play.tempWorkDir != "" && os.RemoveAll(play.tempWorkDir) != nil {
        Printf("临时目录[%s]删除失败，请尝试手动删除 : rm %s -rf\n", play.tempWorkDir, play.tempWorkDir)
    }
}

func (environment serverEnvironment) clearServer(ssh SSH.SysSSH) {
    ssh.Exe("rm /tmp/deployment-*-*-*-*-* -rf")
    Printf("服务器[%s]缓存已清理\n", ssh.Host)
}
